#define _GNU_SOURCE
#include <limits.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/wait.h>
#include <sys/random.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <stdint.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <time.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <linux/if_link.h>
#include <netlink/netlink.h>
#include <netlink/cache.h>
#include <netlink/route/link.h>
#include <cjson/cJSON.h>
#include "hi_sysinfo.h"
#include "hi_ipc.h"
#include "igdCmModulePub.h"
#include "igdCmGlobalModulePub.h"
#include "cdninfo.h"
#include "curl_req.h"

struct hi_cdn_data *g_cdn_data;
hi_sysinfo_data_s g_info;

int get_link_stat(const char *ifname, uint64_t *rxbytes, uint64_t *txbytes, uint8_t *carrier)
{
	struct rtnl_link *link;
	struct nl_sock *socket;

	socket = nl_socket_alloc();
	nl_connect(socket, NETLINK_ROUTE);

	if (rtnl_link_get_kernel(socket, 0, ifname, &link) >= 0) {
		*txbytes += rtnl_link_get_stat(link, RTNL_LINK_TX_BYTES);
		*rxbytes += rtnl_link_get_stat(link, RTNL_LINK_RX_BYTES);
		*carrier = rtnl_link_get_carrier(link);
		rtnl_link_put(link);
	}
	nl_socket_free(socket);
	return 0;
}

int flow_if_info(cJSON *obj, const char *ifname, uint64_t *rx, uint64_t *tx)
{
	uint8_t carrier;
	get_link_stat(ifname, rx, tx, &carrier);
	cJSON *ifinfo = cJSON_AddObjectToObject(obj, ifname);
	cJSON_AddNumberToObject(ifinfo, "rx_bytes", *rx);
	cJSON_AddNumberToObject(ifinfo, "tx_bytes", *tx);
	cJSON_AddNumberToObject(ifinfo, "carrier", carrier);
	return 0;
}

int flow_info(cJSON *obj)
{
	static uint64_t pre_eth0_rx, pre_eth0_tx;
	static uint64_t pre_eth1_rx, pre_eth1_tx;
	static uint64_t pre_eth2_rx, pre_eth2_tx;
	static uint64_t pre_eth3_rx, pre_eth3_tx;
	static uint64_t pre_vap0_rx, pre_vap0_tx;
	static uint64_t pre_vap8_rx, pre_vap8_tx;

	uint64_t cur_eth0_rx, cur_eth0_tx;
	uint64_t cur_eth1_rx, cur_eth1_tx;
	uint64_t cur_eth2_rx, cur_eth2_tx;
	uint64_t cur_eth3_rx, cur_eth3_tx;
	uint64_t cur_vap0_rx, cur_vap0_tx;
	uint64_t cur_vap8_rx, cur_vap8_tx;

	cJSON *flow = cJSON_AddObjectToObject(obj, "flow");
	flow_if_info(flow, "eth0", &cur_eth0_rx, &cur_eth0_tx);
	flow_if_info(flow, "eth1", &cur_eth1_rx, &cur_eth1_tx);
	flow_if_info(flow, "eth2", &cur_eth2_rx, &cur_eth2_tx);
	flow_if_info(flow, "eth3", &cur_eth3_rx, &cur_eth3_tx);
	flow_if_info(flow, "vap0", &cur_vap0_rx, &cur_vap0_tx);
	flow_if_info(flow, "vap8", &cur_vap8_rx, &cur_vap8_tx);

	uint64_t wan_up = 0, wan_down = 0;
	uint64_t user_up = cur_vap0_rx + cur_vap8_rx - pre_vap0_rx - pre_vap8_rx;
	uint64_t user_down = cur_vap0_tx + cur_vap8_tx - pre_vap0_tx - pre_vap8_tx;

	printf("wan=%s\n", g_info.ac_pon_adapt);
	if (strcmp(g_info.ac_pon_adapt, "eth1") == 0) {
		wan_up = cur_eth1_tx - pre_eth1_tx;
		wan_down = cur_eth1_rx - pre_eth1_rx;
		printf("pre_eth1_tx=%ld pre_eth1_rx=%ld\n", pre_eth1_tx, pre_eth1_rx);
		printf("cur_eth1_tx=%ld cur_eth1_rx=%ld\n", cur_eth1_tx, cur_eth1_rx);

		user_up += cur_eth2_rx + cur_eth3_rx - pre_eth2_rx - pre_eth3_rx;
		user_down += cur_eth2_tx + cur_eth3_tx - pre_eth2_tx - pre_eth3_tx;
	} else if (strcmp(g_info.ac_pon_adapt, "eth2") == 0) {
		wan_up = cur_eth2_tx - pre_eth2_tx;
		wan_down = cur_eth2_rx - pre_eth2_rx;

		user_up += cur_eth1_rx + cur_eth3_rx - pre_eth1_rx - pre_eth3_rx;
		user_down += cur_eth1_tx + cur_eth3_tx - pre_eth1_tx - pre_eth3_tx;
	} else if (strcmp(g_info.ac_pon_adapt, "eth3") == 0) {
		wan_up = cur_eth3_tx - pre_eth3_tx;
		wan_down = cur_eth3_rx - pre_eth3_rx;

		user_up += cur_eth2_rx + cur_eth3_rx - pre_eth2_rx - pre_eth3_rx;
		user_down += cur_eth2_tx + cur_eth3_tx - pre_eth2_tx - pre_eth3_tx;
	}

	cJSON *ifinfo = cJSON_AddObjectToObject(flow, "cdn");
	cJSON_AddNumberToObject(ifinfo, "cdn_up", cur_eth0_rx - pre_eth0_rx);
	cJSON_AddNumberToObject(ifinfo, "cdn_down", cur_eth0_tx - pre_eth0_tx);

	ifinfo = cJSON_AddObjectToObject(flow, "wan");
	cJSON_AddNumberToObject(ifinfo, "wan_up", wan_up);
	cJSON_AddNumberToObject(ifinfo, "wan_down", wan_down);

	ifinfo = cJSON_AddObjectToObject(flow, "user");
	cJSON_AddNumberToObject(ifinfo, "user_up", user_up);
	cJSON_AddNumberToObject(ifinfo, "user_down", user_down);

	pre_eth0_rx = cur_eth0_rx;
	pre_eth0_tx = cur_eth0_tx;

	pre_eth1_rx = cur_eth1_rx;
	pre_eth1_tx = cur_eth1_tx;

	pre_eth2_rx = cur_eth2_rx;
	pre_eth2_tx = cur_eth2_tx;

	pre_eth3_rx = cur_eth3_rx;
	pre_eth3_tx = cur_eth3_tx;

	pre_vap0_rx = cur_vap0_rx;
	pre_vap0_tx = cur_vap0_tx;

	pre_vap8_rx = cur_vap8_rx;
	pre_vap8_tx = cur_vap8_tx;

	return 0;
}

int qoslimit_info(cJSON *obj)
{
	int ret = 0;
	IgdQosBasicAttrConfTab dataqos;
	HI_OS_MEMSET_S(&dataqos, sizeof(dataqos), 0, sizeof(dataqos));
	ret = igdCmConfGet(IGD_QOS_BASIC_ATTR_TAB, (unsigned char *)&dataqos, sizeof(dataqos));
	if (ret != 0) {
		return 1;
	}

	cJSON *qos = cJSON_AddObjectToObject(obj, "qos_limit");
	cJSON_AddBoolToObject(qos, "qos_enable", dataqos.ucQosEnable);

	IgdQosFlowLimitAttrConfTab data;
	HI_OS_MEMSET_S(&data, sizeof(data), 0, sizeof(data));
	ret = igdCmConfGet(IGD_QOS_FLOW_LIMIT_ATTR_TAB, (unsigned char *)&data, sizeof(data));
	if (dataqos.ucQosEnable) {
		char *ptr = strchr(data.aucUpLanLimit, '/');
		ptr++;
		char limit[16];
		snprintf(limit, sizeof(limit), "%d", atoi(ptr) / 2);
		cJSON_AddStringToObject(qos, "up_limit", limit);
	} else {
		cJSON_AddStringToObject(qos, "up_limit", "0");
	}

	cJSON_AddStringToObject(qos, "cdn_up", g_cdn_data->cdn_up);

	return 0;
}

int sn_version_info(cJSON *obj)
{
	cJSON_AddStringToObject(obj, "sn", g_info.ac_product_sn);
	cJSON_AddStringToObject(obj, "version", g_info.ac_sw_version);
	struct timespec ts;
	clock_gettime(CLOCK_BOOTTIME, &ts);
	cJSON_AddNumberToObject(obj, "bootup_time", ts.tv_sec);

	IgdLanIPAddrAttrConfTab datalan;
	char ipaddr[INET6_ADDRSTRLEN];
	if (igdCmConfGet(IGD_LAN_IP_ADDR_TAB, (unsigned char *)&datalan, sizeof(datalan))) {
		return 1;
	}
	igdCmApiIptoChar((unsigned char *)&datalan.aucIPv4Addr, ipaddr, sizeof(ipaddr));
	cJSON_AddStringToObject(obj, "mgt_ip", ipaddr);

	struct ifaddrs *ifaddrs;
	getifaddrs(&ifaddrs);
	int family = 0;
	char dym_ip[INET6_ADDRSTRLEN];
	for (struct ifaddrs *ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr == NULL)
			continue;
		family = ifa->ifa_addr->sa_family;
		if (family != AF_INET) {
			continue;
		}
		if (strcmp(ifa->ifa_name, "br0")) {
			continue;
		}
		getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), dym_ip, sizeof(dym_ip), NULL, 0, NI_NUMERICHOST);
		if (strcmp(dym_ip, ipaddr) == 0) {
			continue;
		}
		cJSON_AddStringToObject(obj, "dym_ip", dym_ip);
		break;
	}
	freeifaddrs(ifaddrs);
	return 0;
}

int route_port_map(cJSON *obj)
{
	cJSON *map = cJSON_AddObjectToObject(obj, "port_map");
	cJSON *ta = cJSON_AddObjectToObject(map, "eth0");
	cJSON_AddStringToObject(ta, "alias", "cdn");
	cJSON_AddStringToObject(ta, "type", "cdn");

	if (strcmp(g_info.ac_pon_adapt, "eth1") == 0) {
		ta = cJSON_AddObjectToObject(map, "eth1");
		cJSON_AddStringToObject(ta, "alias", "wan");
		cJSON_AddStringToObject(ta, "type", "wan");

		ta = cJSON_AddObjectToObject(map, "eth2");
		cJSON_AddStringToObject(ta, "alias", "lan1");
		cJSON_AddStringToObject(ta, "type", "lan");

		ta = cJSON_AddObjectToObject(map, "eth3");
		cJSON_AddStringToObject(ta, "alias", "lan2");
		cJSON_AddStringToObject(ta, "type", "lan");

	} else if (strcmp(g_info.ac_pon_adapt, "eth2") == 0) {
		ta = cJSON_AddObjectToObject(map, "eth1");
		cJSON_AddStringToObject(ta, "alias", "lan1");
		cJSON_AddStringToObject(ta, "type", "lan");

		ta = cJSON_AddObjectToObject(map, "eth2");
		cJSON_AddStringToObject(ta, "alias", "wan");
		cJSON_AddStringToObject(ta, "type", "wan");

		ta = cJSON_AddObjectToObject(map, "eth3");
		cJSON_AddStringToObject(ta, "alias", "lan2");
		cJSON_AddStringToObject(ta, "type", "lan");

	} else if (strcmp(g_info.ac_pon_adapt, "eth3") == 0) {
		ta = cJSON_AddObjectToObject(map, "eth1");
		cJSON_AddStringToObject(ta, "alias", "lan1");
		cJSON_AddStringToObject(ta, "type", "lan");

		ta = cJSON_AddObjectToObject(map, "eth2");
		cJSON_AddStringToObject(ta, "alias", "lan2");
		cJSON_AddStringToObject(ta, "type", "lan");

		ta = cJSON_AddObjectToObject(map, "eth3");
		cJSON_AddStringToObject(ta, "alias", "wan");
		cJSON_AddStringToObject(ta, "type", "wan");
	}
	ta = cJSON_AddObjectToObject(map, "vap0");
	cJSON_AddStringToObject(ta, "alias", "2.4G");
	cJSON_AddStringToObject(ta, "type", "wlan");

	ta = cJSON_AddObjectToObject(map, "vap8");
	cJSON_AddStringToObject(ta, "alias", "5G");
	cJSON_AddStringToObject(ta, "type", "wlan");
	return 0;
}

extern int monitor_cdn(char *ip, int len);
int cdn_info(cJSON *obj, const char *cdn_ip)
{
	cJSON_AddStringToObject(obj, "cdn_ip", cdn_ip);

	uint64_t rx = 0, tx = 0;
	uint8_t carrier;
	get_link_stat("eth0", &rx, &tx, &carrier);
	cJSON_AddNumberToObject(obj, "cdn_status", carrier);
	return 0;
}

int route_mode_info(cJSON *obj)
{
	int ret = 0;
	IgdDevGlobalAttrConfTab rdata;
	HI_OS_MEMSET_S(&rdata, sizeof(rdata), 0, sizeof(rdata));
	ret = igdCmConfGet(IGD_GLOBAL_ATTR_TAB, (unsigned char *)&rdata, sizeof(rdata));
	if (ret != 0) {
		return -1;
	}
	if (rdata.ulModeType == DEV_GLOBAL_MODE_BRIDGE) {
		cJSON_AddStringToObject(obj, "mode", "bridge");
	} else {
		cJSON_AddStringToObject(obj, "mode", "route");
	}
	return 0;
}

int rate_info(cJSON *obj)
{
	const int INTERVAL = 20;
	uint64_t pre_eth0_rx = 0, pre_eth0_tx = 0;
	uint64_t pre_eth1_rx = 0, pre_eth1_tx = 0;
	uint64_t pre_eth2_rx = 0, pre_eth2_tx = 0;
	uint64_t pre_eth3_rx = 0, pre_eth3_tx = 0;
	uint64_t pre_vap0_rx = 0, pre_vap0_tx = 0;
	uint64_t pre_vap8_rx = 0, pre_vap8_tx = 0;

	uint64_t cur_eth0_rx = 0, cur_eth0_tx = 0;
	uint64_t cur_eth1_rx = 0, cur_eth1_tx = 0;
	uint64_t cur_eth2_rx = 0, cur_eth2_tx = 0;
	uint64_t cur_eth3_rx = 0, cur_eth3_tx = 0;
	uint64_t cur_vap0_rx = 0, cur_vap0_tx = 0;
	uint64_t cur_vap8_rx = 0, cur_vap8_tx = 0;

	uint8_t carrier;
	get_link_stat("eth0", &pre_eth0_rx, &pre_eth0_tx, &carrier);
	get_link_stat("eth1", &pre_eth1_rx, &pre_eth1_tx, &carrier);
	get_link_stat("eth2", &pre_eth2_rx, &pre_eth2_tx, &carrier);
	get_link_stat("eth3", &pre_eth3_rx, &pre_eth3_tx, &carrier);
	get_link_stat("vap0", &pre_vap0_rx, &pre_vap0_tx, &carrier);
	get_link_stat("vap8", &pre_vap8_rx, &pre_vap8_tx, &carrier);
	sleep(INTERVAL);
	get_link_stat("eth0", &cur_eth0_rx, &cur_eth0_tx, &carrier);
	get_link_stat("eth1", &cur_eth1_rx, &cur_eth1_tx, &carrier);
	get_link_stat("eth2", &cur_eth2_rx, &cur_eth2_tx, &carrier);
	get_link_stat("eth3", &cur_eth3_rx, &cur_eth3_tx, &carrier);
	get_link_stat("vap0", &cur_vap0_rx, &cur_vap0_tx, &carrier);
	get_link_stat("vap8", &cur_vap8_rx, &cur_vap8_tx, &carrier);

	cJSON *rate = cJSON_AddObjectToObject(obj, "rate");
	cJSON *ifrate = cJSON_AddObjectToObject(rate, "eth0");
	cJSON_AddNumberToObject(ifrate, "up", ((cur_eth0_rx - pre_eth0_rx) >> 17) / INTERVAL);
	cJSON_AddNumberToObject(ifrate, "down", ((cur_eth0_tx - pre_eth0_tx) >> 17) / INTERVAL);

	ifrate = cJSON_AddObjectToObject(rate, "eth1");
	cJSON_AddNumberToObject(ifrate, "up", ((cur_eth1_rx - pre_eth1_rx) >> 17) / INTERVAL);
	cJSON_AddNumberToObject(ifrate, "down", ((cur_eth1_tx - pre_eth1_tx) >> 17) / INTERVAL);

	ifrate = cJSON_AddObjectToObject(rate, "eth2");
	cJSON_AddNumberToObject(ifrate, "up", ((cur_eth2_rx - pre_eth2_rx) >> 17) / INTERVAL);
	cJSON_AddNumberToObject(ifrate, "down", ((cur_eth2_tx - pre_eth2_tx) >> 17) / INTERVAL);

	ifrate = cJSON_AddObjectToObject(rate, "eth3");
	cJSON_AddNumberToObject(ifrate, "up", ((cur_eth3_rx - pre_eth3_rx) >> 17) / INTERVAL);
	cJSON_AddNumberToObject(ifrate, "down", ((cur_eth3_tx - pre_eth3_tx) >> 17) / INTERVAL);

	ifrate = cJSON_AddObjectToObject(rate, "vap0");
	cJSON_AddNumberToObject(ifrate, "up", ((cur_vap0_rx - pre_vap0_rx) >> 17) / INTERVAL);
	cJSON_AddNumberToObject(ifrate, "down", ((cur_vap0_tx - pre_vap0_tx) >> 17) / INTERVAL);

	ifrate = cJSON_AddObjectToObject(rate, "vap8");
	cJSON_AddNumberToObject(ifrate, "up", ((cur_vap8_rx - pre_vap8_rx) >> 17) / INTERVAL);
	cJSON_AddNumberToObject(ifrate, "down", ((cur_vap8_tx - pre_vap8_tx) >> 17) / INTERVAL);
	return 0;
}

extern int send_cmd(char *cmd, const char *ip);
int report(const char *cdn_ip)
{
	cJSON *obj = cJSON_CreateObject();
	sn_version_info(obj);
	cdn_info(obj, cdn_ip);
	route_mode_info(obj);
	route_port_map(obj);
	qoslimit_info(obj);
	rate_info(obj);
	flow_info(obj);

	char *ptr = cJSON_Print(obj);
	printf("%s\n", ptr);

	char result[1024];
	call_post_requst("10.110.30.106:18084", "/v/router/report", ptr, result, sizeof(result));
	//call_post_requst("10.110.30.118:8000", "/v/router/report", ptr, result, sizeof(result));
	//call_post_requst("ota.sxjzkeji.com", "/v/router/report", ptr, result, sizeof(result));
	printf("result=%s\n", result);
	free(ptr);
	cJSON_Delete(obj);

	obj = cJSON_Parse(result);
	if (obj == NULL) {
		return 0;
	}
	cJSON *code = cJSON_GetObjectItemCaseSensitive(obj, "code");
	if (!cJSON_IsNumber(code)) {
		goto err;
	}

	if (code->valueint != 200) {
		goto err;
	}

	cJSON *data = cJSON_GetObjectItemCaseSensitive(obj, "data");
	if (!cJSON_IsObject(data)) {
		goto err;
	}
	cJSON *act = cJSON_GetObjectItemCaseSensitive(data, "eth0RebootEnable");
	if (cJSON_IsNumber(act) && act->valueint == 1) {
		hi_os_system("ip link set dev eth0 down");
		sleep(2);
		hi_os_system("ip link set dev eth0 up");
	}

	act = cJSON_GetObjectItemCaseSensitive(data, "cdnRebootEnable");
	if (cJSON_IsNumber(act) && act->valueint == 1) {
		send_cmd("sudo reboot", cdn_ip);
	}

	act = cJSON_GetObjectItemCaseSensitive(data, "routerRebootEnable");
	if (cJSON_IsNumber(act) && act->valueint == 1) {
		hi_os_system("reboot");
	}

	act = cJSON_GetObjectItemCaseSensitive(data, "broadBandConfig");
	if (cJSON_IsNumber(act) && act->valueint != 0) {
		snprintf(g_cdn_data->cdn_up, sizeof(g_cdn_data->cdn_up), "%d", act->valueint);
		msync(g_cdn_data, sizeof(struct hi_cdn_data), MS_SYNC);
		hi_os_system("cp /tmp/cdn_info /config/work/cdn_info");
		g_cdn_data->cdn_set_up[0] = 1;
		msync(g_cdn_data, sizeof(struct hi_cdn_data), MS_SYNC);
		hi_os_system("killall -9 rate_monitor");
		sleep(1);
		hi_os_system("/usr/bin/rate_monitor &");
		hi_os_system("sync");
	}
	return 0;
err:
	cJSON_Delete(obj);
	return 0;
}

int main()
{
	int lfd = open("/var/run/report_status.pid", O_RDWR | O_CREAT, 0666);
	if (flock(lfd, LOCK_EX | LOCK_NB) < 0) {
		perror("master report_status is runing\n");
		return 0;
	}

	signal(SIGTERM, SIG_IGN);

	int sfd = open("/tmp/cdn_info", O_RDWR | O_CREAT, 0666);
	if (sfd < 0) {
		perror("can not create cdn_info");
		exit(0);
	}
	if (ftruncate(sfd, sizeof(struct hi_cdn_data)) < 0) {
		perror("can not alloc shm");
		exit(0);
	}
	g_cdn_data = mmap(NULL, sizeof(struct hi_cdn_data), PROT_WRITE | PROT_READ, MAP_SHARED, sfd, 0);
	if (g_cdn_data == MAP_FAILED) {
		perror("mmap cnd_info");
		exit(0);
	}

	if (HI_IPC_CALL("hi_sysinfo_data_get", &(g_info)) != 0) {
		return 0;
	}

	char cdn_ip[INET_ADDRSTRLEN];
	int pid = 0;
	for (;;) {
		if ((pid = fork()) == 0) {
			while (1) {
				cdn_ip[0] = 0;
				monitor_cdn(cdn_ip, sizeof(cdn_ip));
				report(cdn_ip);
				sleep(60 * 1 - 50);
			}
			exit(0);
		} else {
			waitpid(pid, NULL, 0);
		}
	}

	return 0;
}